import { ref } from "vue";
import { ConditionGroup, Condition, ValueType } from "../types";
import { UseCondition } from "./types";
import { TextValue } from "./condition-value/text-value";
import { CompareType } from "./use-compare";

export function useCondition(): UseCondition {

    const rootGroup = ref<ConditionGroup>({ relation: 0, items: [], children: [], path: [], groupId: 0, level: 0 } as ConditionGroup);
    const conditionGroupMap = new Map<number, ConditionGroup>();
    const groupParentMap = new Map<number, ConditionGroup>();
    let groupNumber = 0;

    function emptyCondition() {
        const emptyCondition = {
            id: '',
            fieldCode: '',
            fieldName: '',
            compareType: CompareType.Equal,
            valueType: ValueType.Value,
            value: new TextValue(),
            relation: 0,
            conditionId: Date.now(),
        } as Condition;
        return emptyCondition;
    };

    function emptyGroup(relation: number, level: number) {
        const conditionGroup: ConditionGroup = { relation, items: [], children: [], path: [], groupId: ++groupNumber, level };
        return conditionGroup;
    }

    function refresh() {
        const root = Object.assign({}, rootGroup.value);
        groupParentMap.forEach((conditionGroup: ConditionGroup, groupId: number) => {
            if (conditionGroup.groupId === root.groupId) {
                groupParentMap.set(groupId, root);
            }
        });
        conditionGroupMap.forEach((conditionGroup: ConditionGroup, groupId: number) => {
            if (conditionGroup.groupId === root.groupId) {
                conditionGroupMap.set(groupId, root);
            }
        });
        rootGroup.value = root;
    }

    function buildIndexDepthMap(conditions: Condition[]) {
        const depthMap = new Map<number, number>();
        let currentLevel = 0;
        conditions.forEach((condition: Condition, conditionIndex: number) => {
            currentLevel += condition.lBracket?.length || 0;
            depthMap.set(conditionIndex, currentLevel);
            currentLevel -= condition.rBracket?.length || 0;
            if (currentLevel < 0) {
                throw new Error('The right bracket is out of range.');
            }
        });
        return depthMap;
    }

    function stretchTo(currentLevel: number, depth: number, currentGroup: ConditionGroup, groupPath: ConditionGroup[], relation: number) {
        for (let index = 0; index < depth; index++) {
            const forwardLevel = currentLevel + (index + 1);
            const newConditionGroup = emptyGroup(relation, forwardLevel);
            currentGroup.children.push(newConditionGroup);
            groupParentMap.set(newConditionGroup.groupId, currentGroup);
            currentGroup = currentGroup.children[currentGroup.children.length - 1];
            groupPath.push(currentGroup);
        }
        return currentGroup;
    }

    function shrinkTo(currentLevel: number, depth: number, currentGroup: ConditionGroup, groupPath: ConditionGroup[], relation: number) {
        for (let index = depth; index <= 0; index++) {
            groupPath.pop();
        }
        const parentGroup = groupPath[groupPath.length - 1];
        if (!parentGroup) {
            throw new Error('The group path is out of range');
        }
        const backwardLevel = currentLevel + depth;
        const newConditionGroup = emptyGroup(relation, backwardLevel);
        parentGroup.children.push(newConditionGroup);
        groupParentMap.set(newConditionGroup.groupId, parentGroup);
        currentGroup = parentGroup.children[parentGroup.children.length - 1];
        groupPath.push(currentGroup);
        return currentGroup;
    }

    function addSibling(currentLevel: number, currentGroup: ConditionGroup, groupPath: ConditionGroup[], relation: number) {
        groupPath.pop();
        const parentGroup = groupPath[groupPath.length - 1];
        const newConditionGroup = emptyGroup(relation, currentLevel);
        parentGroup.children.push(newConditionGroup);
        groupParentMap.set(newConditionGroup.groupId, parentGroup);
        currentGroup = parentGroup.children[parentGroup.children.length - 1];
        groupPath.push(currentGroup);
        return currentGroup;
    }

    function getCurrentDepthGroup(
        groupPath: ConditionGroup[], depth: Map<number, number>, preIndex: number, currentIndex: number,
        preCondition: Condition, currentConditon: Condition
    ): ConditionGroup {
        let currentGroup = groupPath[groupPath.length - 1];
        if (!currentGroup) {
            throw new Error('The group path is out of range');
        }

        const currentDepth = depth.get(currentIndex) || 0;
        const preDepth = depth.get(preIndex) || 0;
        if (preIndex < 0 || preIndex === currentIndex) {
            return groupPath[groupPath.length - 1];
        }

        const step = currentDepth - preDepth;
        const relation = preCondition ? (preCondition.relation !== undefined ? preCondition.relation : 1) : 1;
        const shouldStretchCurrentGroup = step > 0;
        currentGroup = shouldStretchCurrentGroup ? stretchTo(preDepth, step, currentGroup, groupPath, relation) : currentGroup;

        const shouldShrinkCurrentGroup = step < 0;
        currentGroup = shouldShrinkCurrentGroup ? shrinkTo(preDepth, step, currentGroup, groupPath, relation) : currentGroup;

        const shouldAddSiblingGroup = step === 0 && preDepth > 0 && currentDepth > 0 && preCondition && currentConditon &&
            !!preCondition.rBracket && !!currentConditon.lBracket;
        currentGroup = shouldAddSiblingGroup ? addSibling(preDepth, currentGroup, groupPath, relation) : currentGroup;

        return currentGroup;
    }

    function loadConditionGroup(conditions: Condition[]) {
        const relation = conditions.length ? (conditions[0].relation !== undefined ? conditions[0].relation : 1) : 1;
        const root = { relation, items: [], children: [], path: [], groupId: 0, level: 0 } as ConditionGroup;
        const groupPath: ConditionGroup[] = [root];
        const depth = buildIndexDepthMap(conditions);

        conditions.forEach((currentConditon: Condition, currentIndex: number) => {
            const preIndex = currentIndex - 1;
            const preCondition = conditions[preIndex];
            const currentGroup = getCurrentDepthGroup(groupPath, depth, preIndex, currentIndex, preCondition, currentConditon);
            currentGroup.items.push(currentConditon);
            conditionGroupMap.set(currentConditon.conditionId, currentGroup);
        });
        rootGroup.value = root;
        return root;
    }

    function addCondition() {
        const newCondition = emptyCondition();
        conditionGroupMap.set(newCondition.conditionId, rootGroup.value);
        rootGroup.value.items.push(newCondition);
    }

    function insertConditionTo(preCondtion: Condition) {
        if (!preCondtion) {
            return;
        }
        const targetGroup = conditionGroupMap.get(preCondtion.conditionId);
        if (targetGroup) {
            const indexToInsert = targetGroup.items.findIndex((item: Condition) => item.conditionId === preCondtion.conditionId) + 1;
            const newCondition = emptyCondition();
            conditionGroupMap.set(newCondition.conditionId, targetGroup);
            targetGroup.items.splice(indexToInsert, 0, newCondition);
            refresh();
        }
    }

    function removeCondition(targetToRemove: Condition) {
        const targetGroup = conditionGroupMap.get(targetToRemove.conditionId);
        if (targetGroup) {
            targetGroup.items = targetGroup.items
                .filter((condition: Condition) => condition.conditionId !== targetToRemove.conditionId);
            conditionGroupMap.delete(targetToRemove.conditionId);
            refresh();
        }
    }

    function separateTargetsToGroup(
        targetsToGroup: (Condition | ConditionGroup)[],
        targetMap: { items: Map<number, Condition>; groups: Map<number, ConditionGroup> },
        itemsToGroup: Condition[],
        subGroupsToGroup: ConditionGroup[]
    ) {
        targetsToGroup.reduce((
            result: { items: Map<number, Condition>; groups: Map<number, ConditionGroup> },
            targetToGroup: Condition | ConditionGroup
        ) => {
            const targetIsQueryCondition = (targetToGroup as Condition).conditionId !== undefined;
            const targetIsConditionGroup = (targetToGroup as ConditionGroup).groupId !== undefined;

            if (targetIsQueryCondition) {
                const itemToGroup = targetToGroup as Condition;
                itemsToGroup.push(itemToGroup);
                result.items.set(itemToGroup.conditionId, itemToGroup);
            }

            if (targetIsConditionGroup) {
                const subGroupToGroup = targetToGroup as ConditionGroup;
                subGroupsToGroup.push(subGroupToGroup);
                result.groups.set(subGroupToGroup.groupId, subGroupToGroup);
            }

            return result;
        }, targetMap);
    }

    function removeTargesFromGroup(
        parentGroup: ConditionGroup,
        itemsToRemove: Condition[],
        itemsToRemoveMap: Map<number, Condition>,
        subGroupsToRemove: ConditionGroup[],
        subGroupsToRemoveMap: Map<number, ConditionGroup>
    ) {
        if (parentGroup) {
            parentGroup.items = itemsToRemove.length ?
                parentGroup.items.filter((item: Condition) => !itemsToRemoveMap.has(item.conditionId)) :
                parentGroup.items;

            parentGroup.children = subGroupsToRemove.length ?
                parentGroup.children.filter((group: ConditionGroup) => !subGroupsToRemoveMap.has(group.groupId)) :
                parentGroup.children;
        }
    }

    function attachToGroup(parentGroup: ConditionGroup, itemsToGroup: Condition[], subGroupsToGroup: ConditionGroup[]) {
        itemsToGroup.forEach((itemToGroup: Condition) => {
            parentGroup.items.push(itemToGroup);
            conditionGroupMap.set(itemToGroup.conditionId, parentGroup);
        });
        subGroupsToGroup.forEach((subGroupToGroup: ConditionGroup) => {
            parentGroup.children.push(subGroupToGroup);
            groupParentMap.set(subGroupToGroup.groupId, parentGroup);
        });
    }

    function group(targetsToGroup: (Condition | ConditionGroup)[]) {
        const firstItem = targetsToGroup[0];
        const parentGroup = (firstItem as Condition).conditionId !== undefined ?
            conditionGroupMap.get((firstItem as Condition).conditionId) :
            groupParentMap.get((firstItem as ConditionGroup).groupId);

        const targetMap = { items: new Map<number, Condition>(), groups: new Map<number, ConditionGroup>() };
        const itemsToGroup: Condition[] = [];
        const subGroupsToGroup: ConditionGroup[] = [];

        if (parentGroup) {
            separateTargetsToGroup(targetsToGroup, targetMap, itemsToGroup, subGroupsToGroup);

            removeTargesFromGroup(parentGroup, itemsToGroup, targetMap.items, subGroupsToGroup, targetMap.groups);

            const newConditionGroup = emptyGroup(parentGroup.relation, parentGroup.level + 1);
            attachToGroup(newConditionGroup, itemsToGroup, subGroupsToGroup);

            parentGroup.children.push(newConditionGroup);
            groupParentMap.set(newConditionGroup.groupId, parentGroup);
            refresh();
        }
    }

    function unGroup(groupToDisperse: ConditionGroup) {
        const conditionItems = [...groupToDisperse.items];
        const conditionGroups = [...groupToDisperse.children];
        const parentGroup = groupParentMap.get(groupToDisperse.groupId);
        if (parentGroup) {
            parentGroup.children = parentGroup.children
                .filter((conditionGroup: ConditionGroup) => conditionGroup.groupId !== groupToDisperse.groupId);
            groupParentMap.delete(groupToDisperse.groupId);

            conditionItems.forEach((conditionItem: Condition) => {
                parentGroup.items.push(conditionItem);
                conditionGroupMap.set(conditionItem.conditionId, parentGroup);
            });

            conditionGroups.forEach((conditionGroup: ConditionGroup) => {
                parentGroup.children.push(conditionGroup);
                groupParentMap.set(conditionGroup.groupId, parentGroup);
            });
            refresh();
        }
    }

    function changeGroupRelation(targetGroup: ConditionGroup) {
        targetGroup.relation = targetGroup.relation === 1 ? 2 : 1;
        refresh();
    }

    let getConditions: (conditionGroup: ConditionGroup) => Condition[];

    function getNestedConditions(conditionGroup: ConditionGroup) {
        const { relation } = conditionGroup;
        const nestedConditions = conditionGroup.children && conditionGroup.children.length ?
            conditionGroup.children
                .map((conditionGroup: ConditionGroup, index: number, allGroup: ConditionGroup[]) => {
                    const conditions = getConditions(conditionGroup);
                    conditions[0].lBracket = (conditions[0].lBracket || '') + '(';
                    conditions[conditions.length - 1].rBracket = (conditions[conditions.length - 1].rBracket || '') + ')';
                    conditions[conditions.length - 1].relation = index < allGroup.length - 1 ? relation : 0;
                    return conditions;
                }) : [];
        return nestedConditions.flat();
    }

    getConditions = (conditionGroup: ConditionGroup = rootGroup.value) => {
        const { relation } = conditionGroup;
        const conditions = conditionGroup.items.map((item: Condition, index: number, allConditions: Condition[]) => {
            item.lBracket = index === 0 ? '(' : '';
            item.rBracket = index === allConditions.length - 1 ? ')' : '';
            item.relation = index < allConditions.length - 1 ? (relation || 1) : 0;
            return item;
        });
        const nestedConditions = getNestedConditions(conditionGroup);
        return [...conditions, ...nestedConditions];
    };

    return {
        addCondition, changeGroupRelation, conditionGroupMap, getConditions, group, groupParentMap, insertConditionTo,
        loadConditionGroup, refresh, removeCondition, rootGroup, unGroup
    };
}
